<script type="module">
  const EIDOS_CONFIG = {
    httpEndpoint: "http://${{currentSpace}}.eidos.localhost:13127/rpc", // Replace with your HTTP endpoint
    timeout: 30000, // HTTP request timeout in milliseconds
  }

  // __BinaryDataUtils__

  // Replace window.fetch with proxy-enabled fetch
  const originalFetch = window.fetch

  window.fetch = async function (input, init = {}) {
    const url = typeof input === "string" ? input : input.url

    // Check if this is a cross-origin request that needs proxying
    try {
      const requestUrl = new URL(url, window.location.href)
      const currentOrigin = window.location.origin
      const requestOrigin = requestUrl.origin

      // If it's a same-origin request or already going through our proxy, use original fetch
      if (
        requestOrigin === currentOrigin ||
        requestUrl.hostname === "proxy.eidos.localhost" ||
        requestUrl.hostname === "localhost" ||
        requestUrl.hostname.endsWith(".eidos.localhost")
      ) {
        return originalFetch(input, init)
      }

      // For cross-origin requests, use our proxy
      const proxyUrl = `http://proxy.eidos.localhost:13127/?url=${encodeURIComponent(url)}`

      // Forward the request through proxy
      return originalFetch(proxyUrl, {
        ...init,
        headers: {
          ...init.headers,
          // Preserve original headers but let proxy handle CORS
        },
      })
    } catch (error) {
      // If URL parsing fails, fall back to original fetch
      console.warn(
        "Failed to parse URL for proxy, using original fetch:",
        error
      )
      return originalFetch(input, init)
    }
  }

  const makeSpaceProxy = (space, bindings = {}) => {
    return new Proxy(
      {},
      {
        get: (target, method) => {
          const isParentAvailable = () => {
            try {
              return window.parent && window.parent !== window
            } catch (e) {
              return false
            }
          }

          const createTransport = () => {
            if (isParentAvailable()) {
              // PostMessage Transport
              const msgChannel = new MessageChannel()
              return {
                send: (data) => {
                  console.log("send via postMessage", data)
                  window.parent.postMessage(
                    {
                      type: "rpcCall",
                      data,
                    },
                    "*",
                    [msgChannel.port2]
                  )
                  return msgChannel.port1
                },
                close: () => msgChannel.port1.close(),
              }
            } else {
              // HTTP Transport
              return {
                send: async (requestData) => {
                  const controller = new AbortController()
                  const timeoutId = setTimeout(
                    () => controller.abort(),
                    EIDOS_CONFIG.timeout
                  )
                  try {
                    // Check if request contains binary data (ArrayBuffer, Blob, File, etc.)
                    const hasBinaryData = containsBinaryData(requestData)

                    let body, headers
                    if (hasBinaryData) {
                      // Use FormData for binary data
                      const formData = new FormData()
                      formData.append("json", JSON.stringify(requestData))

                      // Extract binary data and add as separate fields
                      let binaryIndex = 0
                      const processedData = processBinaryData(
                        requestData,
                        (binaryData) => {
                          const fieldName = `binary_${binaryIndex++}`
                          formData.append(fieldName, binaryData)
                          return fieldName
                        }
                      )

                      // Update the JSON data to reference binary fields
                      formData.set("json", JSON.stringify(processedData))

                      body = formData
                      headers = {} // Let browser set Content-Type for FormData
                    } else {
                      // Use JSON for regular data
                      body = JSON.stringify(requestData)
                      headers = { "Content-Type": "application/json" }
                    }

                    const response = await fetch(EIDOS_CONFIG.httpEndpoint, {
                      method: "POST",
                      headers,
                      body,
                      signal: controller.signal,
                    })
                    clearTimeout(timeoutId)
                    if (!response.ok) {
                      throw new Error(`HTTP error: ${response.status}`)
                    }

                    // Check if response is multipart (contains binary data)
                    const contentType = response.headers.get("content-type")
                    let responseData

                    if (
                      contentType &&
                      contentType.includes("multipart/form-data")
                    ) {
                      // Handle binary response
                      const formData = await response.formData()
                      const jsonData = JSON.parse(formData.get("json") || "{}")

                      if (!jsonData.success) {
                        throw new Error(jsonData.error || "RPC call failed")
                      }

                      // Extract binary data from form fields
                      const binaryDataMap = {}
                      for (const [key, value] of formData.entries()) {
                        if (
                          key.startsWith("binary_") &&
                          value instanceof Blob
                        ) {
                          // Convert Blob to ArrayBuffer for processing
                          const arrayBuffer = await value.arrayBuffer()
                          binaryDataMap[key] = {
                            data: arrayBuffer,
                            type: value.type,
                            size: value.size,
                          }
                        }
                      }

                      // Restore binary data in the result
                      responseData = restoreBinaryData(
                        jsonData.data,
                        binaryDataMap
                      )
                    } else {
                      // Regular JSON response
                      const jsonData = await response.json()
                      if (!jsonData.success) {
                        throw new Error(jsonData.error || "RPC call failed")
                      }
                      responseData = jsonData.data
                    }

                    console.log("data", responseData)
                    const simulatedPort = {
                      onmessage: null,
                      close: () => { },
                      postMessage: () => { },
                    }
                    setTimeout(() => {
                      if (simulatedPort.onmessage) {
                        simulatedPort.onmessage({
                          data: { type: "rpcCallResp", data: responseData },
                        })
                      }
                    }, 0)
                    return simulatedPort
                  } catch (error) {
                    clearTimeout(timeoutId)
                    throw error
                  }
                },
                close: () => { },
              }
            }
          }

          const onCallBack = (port) => {
            return new Promise((resolve, reject) => {
              port.onmessage = (event) => {
                port.close()
                console.log("onCallBack", event)
                const { type, data } = event.data
                if (type === "rpcCallResp") {
                  resolve(data)
                } else {
                  reject(new Error("RPC call failed"))
                }
              }
            })
          }

          if (bindings[method]) {
            const tableId = bindings[method].value
            return new Proxy(
              {},
              {
                get(target, subMethod) {
                  if (subMethod === "rows") {
                    return new Proxy(
                      {},
                      {
                        get(target, method) {
                          return async function (...params) {
                            const [_params, ...rest] = params
                            const transport = createTransport()
                            const port = await transport.send({
                              method: `table(${tableId}).rows.${method}`,
                              params: [_params, ...rest],
                              space,
                            })
                            return onCallBack(port)
                          }
                        },
                      }
                    )
                  }
                  return async function (...params) {
                    const [_params, ...rest] = params
                    const transport = createTransport()
                    const port = await transport.send({
                      method: `table("${tableId}").${subMethod}`,
                      params: [_params, ...rest],
                      space,
                    })
                    return onCallBack(port)
                  }
                },
              }
            )
          }

          if (method === "table" || /^[A-Z][A-Za-z0-9_]*$/.test(method)) {
            return function (id) {
              const tableId = /^[A-Z]/.test(method) ? method : id
              return new Proxy(
                {},
                {
                  get(target, method) {
                    if (method === "rows") {
                      return new Proxy(
                        {},
                        {
                          get(target, method) {
                            return async function (...params) {
                              const [_params, ...rest] = params
                              const transport = createTransport()
                              const port = await transport.send({
                                method: `table(${tableId}).rows.${method}`,
                                params: [_params, ...rest],
                                space,
                              })
                              return onCallBack(port)
                            }
                          },
                        }
                      )
                    }
                    return async function (...params) {
                      const [_params, ...rest] = params
                      const transport = createTransport()
                      const port = await transport.send({
                        method: `table("${id}").${method}`,
                        params: [_params, ...rest],
                        space,
                      })
                      return onCallBack(port)
                    }
                  },
                }
              )
            }
          }

          if (
            [
              "doc",
              "action",
              "script",
              "extension",
              "tree",
              "view",
              "column",
              "embedding",
              "file",
              "extNode",
              "theme",
              "dataView",
              "kv",
              "fs",
            ].includes(method)
          ) {
            return new Proxy(
              {},
              {
                get(target, subMethod) {
                  return async function (...params) {
                    const [_params, ...rest] = params
                    const transport = createTransport()
                    console.log("transport", transport)
                    const port = await transport.send({
                      method: `${method}.${subMethod}`,
                      params: [_params, ...rest],
                      space,
                    })
                    console.log("port", port)
                    return onCallBack(port)
                  }
                },
              }
            )
          }

          return async (...params) => {
            const transport = createTransport()
            const port = await transport.send({
              space,
              method,
              params,
            })
            return onCallBack(port)
          }
        },
      }
    )
  }

  function createFunction(scope, method) {
    const isParentAvailable = () => {
      try {
        return window.parent && window.parent !== window
      } catch (e) {
        return false
      }
    }

    const createTransport = () => {
      if (isParentAvailable()) {
        // PostMessage Transport
        const msgChannel = new MessageChannel()
        return {
          send: (data) => {
            console.log("send via postMessage", data)
            window.parent.postMessage(
              {
                type: "scriptCallMain",
                data,
              },
              "*",
              [msgChannel.port2]
            )
            return msgChannel.port1
          },
          close: () => msgChannel.port1.close(),
        }
      } else {
        // HTTP Transport
        return {
          send: async (requestData) => {
            const controller = new AbortController()
            const timeoutId = setTimeout(
              () => controller.abort(),
              EIDOS_CONFIG.timeout
            )
            try {
              // Check if request contains binary data (ArrayBuffer, Blob, File, etc.)
              const hasBinaryData = containsBinaryData(requestData)

              let body, headers
              if (hasBinaryData) {
                // Use FormData for binary data
                const formData = new FormData()
                formData.append("json", JSON.stringify(requestData))

                // Extract binary data and add as separate fields
                let binaryIndex = 0
                const processedData = processBinaryData(
                  requestData,
                  (binaryData) => {
                    const fieldName = `binary_${binaryIndex++}`
                    formData.append(fieldName, binaryData)
                    return fieldName
                  }
                )

                // Update the JSON data to reference binary fields
                formData.set("json", JSON.stringify(processedData))

                body = formData
                headers = {} // Let browser set Content-Type for FormData
              } else {
                // Use JSON for regular data
                body = JSON.stringify(requestData)
                headers = { "Content-Type": "application/json" }
              }

              const response = await fetch(EIDOS_CONFIG.httpEndpoint, {
                method: "POST",
                headers,
                body,
                signal: controller.signal,
              })
              clearTimeout(timeoutId)
              if (!response.ok) {
                throw new Error(`HTTP error: ${response.status}`)
              }

              // Check if response is multipart (contains binary data)
              const contentType = response.headers.get("content-type")
              let responseData

              if (contentType && contentType.includes("multipart/form-data")) {
                // Handle binary response
                const formData = await response.formData()
                const jsonData = JSON.parse(formData.get("json") || "{}")

                if (!jsonData.success) {
                  throw new Error(jsonData.error || "Script call failed")
                }

                // Extract binary data from form fields
                const binaryDataMap = {}
                for (const [key, value] of formData.entries()) {
                  if (key.startsWith("binary_") && value instanceof Blob) {
                    // Convert Blob to ArrayBuffer for processing
                    const arrayBuffer = await value.arrayBuffer()
                    binaryDataMap[key] = {
                      data: arrayBuffer,
                      type: value.type,
                      size: value.size,
                    }
                  }
                }

                // Restore binary data in the result
                responseData = restoreBinaryData(jsonData.data, binaryDataMap)
              } else {
                // Regular JSON response
                const jsonData = await response.json()
                if (!jsonData.success) {
                  throw new Error(jsonData.error || "Script call failed")
                }
                responseData = jsonData.data
              }

              const simulatedPort = {
                onmessage: null,
                close: () => { },
                postMessage: () => { },
              }
              setTimeout(() => {
                if (simulatedPort.onmessage) {
                  simulatedPort.onmessage({
                    data: { type: "scriptCallMainResp", data: responseData },
                  })
                }
              }, 0)
              return simulatedPort
            } catch (error) {
              clearTimeout(timeoutId)
              throw error
            }
          },
          close: () => { },
        }
      }
    }

    const run = async (...args) => {
      const transport = createTransport()
      const port = await transport.send({
        scope,
        method,
        args,
      })
      return new Promise((resolve, reject) => {
        port.onmessage = (event) => {
          const { type, data } = event.data
          if (type === `scriptCallMainResp`) {
            resolve(data)
          }
          if (type === `scriptCallMainError`) {
            reject(data)
          }
          port.close()
        }
      })
    }

    return (...args) => run(...args)
  }

  window.eidos = new Proxy(
    {},
    {
      get: (target, prop) => {
        if (prop === "space") {
          return (space, bindings) => {
            return makeSpaceProxy(space, bindings)
          }
        }
        if (prop === "currentSpace") {
          return makeSpaceProxy(
            "${{currentSpace}}",
            window.__EIDOS_BINDINGS__ || {}
          )
        }
        if (prop === "script") {
          return {
            call: (...args) => {
              return createFunction("script", "callScript")(...args)
            },
          }
        }
        if (prop === "AI" || prop === "ai") {
          return {
            generateText: (...args) => {
              return createFunction("AI", "generateText")(...args)
            },
            generateObject: (...args) => {
              return createFunction("AI", "generateObject")(...args)
            },
          }
        }
        if (prop === "utils") {
          return {
            tableHighlightRow: (...args) => {
              return createFunction("utils", "tableHighlightRow")(...args)
            },
            fetchBlob: (...args) => {
              return createFunction("utils", "fetchBlob")(...args)
            },
          }
        }
      },
    }
  )

  let modules = {}

  window.addEventListener("message", async (event) => {
    const { type, data } = event.data
    const port = event.ports[0]
    if (type === "ScriptFunctionCall") {
      const { input, context, code, command, bindings, id, space } = data
      for (const key in bindings) {
        window.__EIDOS_BINDINGS__ = bindings
      }

      const env = Object.entries(bindings || {}).reduce((acc, [key, binding]) => {
        if (binding.type === "secret" || binding.type === "text") {
          acc[key] = binding.value
        }
        return acc
      }, {})

      context.env = env

      try {
        const module = await await import(`/${id}.js`)
        const res = await module[command](input, context)
        port.postMessage({
          type: "ScriptFunctionCallResponse",
          data: res,
        })
      } catch (error) {
        port.postMessage({
          type: "ScriptFunctionCallError",
          data: error,
        })
      }
    }
  })
</script>